{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {
    "deletable": true,
    "editable": true
   },
   "source": [
    "# Recommending movies: ranking\n",
    "\n",
    "## Learning Objectives\n",
    "\n",
    "1. Get our data and split it into a training and test set.\n",
    "2. Implement a ranking model.\n",
    "3. Fit and evaluate it.\n",
    "\n",
    "\n",
    "\n",
    "\n",
    "## Introduction \n",
    "The retrieval stage is responsible for selecting an initial set of hundreds of candidates from all possible candidates. The main objective of this model is to efficiently weed out all candidates that the user is not interested in. Because the retrieval model may be dealing with millions of candidates, it has to be computationally efficient.\n",
    "The ranking stage takes the outputs of the retrieval model and fine-tunes them to select the best possible handful of recommendations. Its task is to narrow down the set of items the user may be interested in to a shortlist of likely candidates.\n",
    "\n",
    "Each learning objective will correspond to a _#TODO_ in this student lab notebook -- try to complete this notebook first and then review the [solution notebook](../solutions/basic_ranking.ipynb)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "sawo1x8kQk9b"
   },
   "source": [
    "## Imports\n",
    "\n",
    "\n",
    "Let's first get our imports out of the way."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {
    "execution": {
     "iopub.execute_input": "2021-03-11T12:08:21.286268Z",
     "iopub.status.busy": "2021-03-11T12:08:21.284145Z",
     "iopub.status.idle": "2021-03-11T12:08:41.017963Z",
     "shell.execute_reply": "2021-03-11T12:08:41.018341Z"
    },
    "id": "9gG3jLOGbaUv"
   },
   "outputs": [],
   "source": [
    "!pip install -q tensorflow-recommenders\n",
    "!pip install -q --upgrade tensorflow-datasets"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "sawo1x8kQk9b"
   },
   "source": [
    "**Note: Please ignore the incompatibility errors and re-run the above cell before proceeding for the lab.**\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {
    "execution": {
     "iopub.execute_input": "2021-03-11T12:08:41.023064Z",
     "iopub.status.busy": "2021-03-11T12:08:41.022474Z",
     "iopub.status.idle": "2021-03-11T12:08:48.236482Z",
     "shell.execute_reply": "2021-03-11T12:08:48.236930Z"
    },
    "id": "SZGYDaF-m5wZ"
   },
   "outputs": [],
   "source": [
    "# You can use any Python source file as a module by executing an import statement in some other Python source file.\n",
    "# The import statement combines two operations; it searches for the named module, then it binds the\n",
    "# results of that search to a name in the local scope.\n",
    "import os\n",
    "import pprint\n",
    "import tempfile\n",
    "\n",
    "from typing import Dict, Text\n",
    "\n",
    "import numpy as np\n",
    "import tensorflow as tf\n",
    "import tensorflow_datasets as tfds"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {
    "execution": {
     "iopub.execute_input": "2021-03-11T12:08:48.240960Z",
     "iopub.status.busy": "2021-03-11T12:08:48.240350Z",
     "iopub.status.idle": "2021-03-11T12:08:48.249364Z",
     "shell.execute_reply": "2021-03-11T12:08:48.248824Z"
    },
    "id": "BxQ_hy7xPH3N"
   },
   "outputs": [],
   "source": [
    "import tensorflow_recommenders as tfrs"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "This notebook uses TF2.x.\n",
    "Please check your tensorflow version using the cell below."
   ]
  },
  {
 "cell_type": "code",
 "execution_count": 5,
 "metadata": {},
 "outputs": [
  {
   "name": "stdout",
   "output_type": "stream",
   "text": [
    "TensorFlow version:  2.6.0\n"
   ]
  }
 ],
 "source": [
  "# Show the currently installed version of TensorFlow\n",
  "print(\"TensorFlow version: \",tf.version.VERSION)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "5PAqjR4a1RR4"
   },
   "source": [
    "## Lab Task 1: Preparing the dataset\n",
    "\n",
    "We're going to use the same data as the [retrieval](https://www.tensorflow.org/recommenders/examples/basic_retrieval) tutorial. This time, we're also going to keep the ratings: these are the objectives we are trying to predict."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {
    "execution": {
     "iopub.execute_input": "2021-03-11T12:08:48.253974Z",
     "iopub.status.busy": "2021-03-11T12:08:48.253401Z",
     "iopub.status.idle": "2021-03-11T12:08:49.346238Z",
     "shell.execute_reply": "2021-03-11T12:08:49.346621Z"
    },
    "id": "aaQhqcLGP0jL"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\u001b[1mDownloading and preparing dataset 4.70 MiB (download: 4.70 MiB, generated: 32.41 MiB, total: 37.10 MiB) to /home/jupyter/tensorflow_datasets/movielens/100k-ratings/0.1.0...\u001b[0m\n"
     ]
    },
    {
     "data": {
      "application/vnd.jupyter.widget-view+json": {
       "model_id": "364d39bae7114a17abd75c1c784051e0",
       "version_major": 2,
       "version_minor": 0
      },
      "text/plain": [
       "Dl Completed...: 0 url [00:00, ? url/s]"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "application/vnd.jupyter.widget-view+json": {
       "model_id": "fed8c9592c2a408795d9aafe14534506",
       "version_major": 2,
       "version_minor": 0
      },
      "text/plain": [
       "Dl Size...: 0 MiB [00:00, ? MiB/s]"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "application/vnd.jupyter.widget-view+json": {
       "model_id": "0961790f0d3943b9baccd02ec22cb8c0",
       "version_major": 2,
       "version_minor": 0
      },
      "text/plain": [
       "Extraction completed...: 0 file [00:00, ? file/s]"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "application/vnd.jupyter.widget-view+json": {
       "model_id": "",
       "version_major": 2,
       "version_minor": 0
      },
      "text/plain": [
       "Generating splits...:   0%|          | 0/1 [00:00<?, ? splits/s]"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "application/vnd.jupyter.widget-view+json": {
       "model_id": "",
       "version_major": 2,
       "version_minor": 0
      },
      "text/plain": [
       "Generating train examples...:   0%|          | 0/100000 [00:00<?, ? examples/s]"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "application/vnd.jupyter.widget-view+json": {
       "model_id": "",
       "version_major": 2,
       "version_minor": 0
      },
      "text/plain": [
       "Shuffling movielens-train.tfrecord...:   0%|          | 0/100000 [00:00<?, ? examples/s]"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\u001b[1mDataset movielens downloaded and prepared to /home/jupyter/tensorflow_datasets/movielens/100k-ratings/0.1.0. Subsequent calls will reuse this data.\u001b[0m\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "2021-09-27 17:05:01.517007: I tensorflow/core/common_runtime/process_util.cc:146] Creating new thread pool with default inter op setting: 2. Tune using inter_op_parallelism_threads for best performance.\n"
     ]
    }
   ],
   "source": [
    "ratings = tfds.load(\"movielens/100k-ratings\", split=\"train\")\n",
    "\n",
    "ratings = ratings.map(lambda x: {\n",
    "    \"movie_title\": x[\"movie_title\"],\n",
    "    \"user_id\": x[\"user_id\"],\n",
    "    \"user_rating\": x[\"user_rating\"]\n",
    "})"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "Iu4XSa_G1nyN"
   },
   "source": [
    "As before, we'll split the data by putting 80% of the ratings in the train set, and 20% in the test set."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {
    "execution": {
     "iopub.execute_input": "2021-03-11T12:08:49.351523Z",
     "iopub.status.busy": "2021-03-11T12:08:49.350936Z",
     "iopub.status.idle": "2021-03-11T12:08:49.354237Z",
     "shell.execute_reply": "2021-03-11T12:08:49.354584Z"
    },
    "id": "rS0eDfkjnjJL"
   },
   "outputs": [],
   "source": [
    "tf.random.set_seed(42)\n",
    "shuffled = ratings.shuffle(100_000, seed=42, reshuffle_each_iteration=False)\n",
    "\n",
    "# TODO 1a -- your code goes here"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "gVi1HJfR9D7H"
   },
   "source": [
    "Let's also figure out unique user ids and movie titles present in the data. \n",
    "\n",
    "This is important because we need to be able to map the raw values of our categorical features to embedding vectors in our models. To do that, we need a vocabulary that maps a raw feature value to an integer in a contiguous range: this allows us to look up the corresponding embeddings in our embedding tables."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {
    "execution": {
     "iopub.execute_input": "2021-03-11T12:08:49.360362Z",
     "iopub.status.busy": "2021-03-11T12:08:49.359693Z",
     "iopub.status.idle": "2021-03-11T12:08:57.816949Z",
     "shell.execute_reply": "2021-03-11T12:08:57.817365Z"
    },
    "id": "MKROCiPo_5LJ"
   },
   "outputs": [],
   "source": [
    "movie_titles = ratings.batch(1_000_000).map(lambda x: x[\"movie_title\"])\n",
    "user_ids = ratings.batch(1_000_000).map(lambda x: x[\"user_id\"])\n",
    "\n",
    "unique_movie_titles = np.unique(np.concatenate(list(movie_titles)))\n",
    "unique_user_ids = np.unique(np.concatenate(list(user_ids)))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "4-Vj9nHb48pn"
   },
   "source": [
    "## Lab Task 2: Implementing a model"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "eCi-seR86qqa"
   },
   "source": [
    "### Architecture\n",
    "\n",
    "Ranking models do not face the same efficiency constraints as retrieval models do, and so we have a little bit more freedom in our choice of architectures.\n",
    "\n",
    "A model composed of multiple stacked dense layers is a relatively common architecture for ranking tasks. We can implement it as follows:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {
    "execution": {
     "iopub.execute_input": "2021-03-11T12:08:57.825651Z",
     "iopub.status.busy": "2021-03-11T12:08:57.824998Z",
     "iopub.status.idle": "2021-03-11T12:08:57.827137Z",
     "shell.execute_reply": "2021-03-11T12:08:57.826715Z"
    },
    "id": "fAk0y0Yf1eGh"
   },
   "outputs": [],
   "source": [
    "class RankingModel(tf.keras.Model):\n",
    "\n",
    "  def __init__(self):\n",
    "    super().__init__()\n",
    "    embedding_dimension = 32\n",
    "\n",
    "    # Compute embeddings for users.\n",
    "\n",
    "    # TODO 2a -- your code goes here\n",
    "\n",
    "    # Compute embeddings for movies.\n",
    "\n",
    "    # TODO 2b -- your code goes here\n",
    "\n",
    "    # Compute predictions.\n",
    "    self.ratings = tf.keras.Sequential([\n",
    "      # Learn multiple dense layers.\n",
    "      tf.keras.layers.Dense(256, activation=\"relu\"),\n",
    "      tf.keras.layers.Dense(64, activation=\"relu\"),\n",
    "      # Make rating predictions in the final layer.\n",
    "      tf.keras.layers.Dense(1)\n",
    "  ])\n",
    "    \n",
    "  def call(self, inputs):\n",
    "\n",
    "    user_id, movie_title = inputs\n",
    "\n",
    "    user_embedding = self.user_embeddings(user_id)\n",
    "    movie_embedding = self.movie_embeddings(movie_title)\n",
    "\n",
    "    return self.ratings(tf.concat([user_embedding, movie_embedding], axis=1))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "g76wZt-s2WmS"
   },
   "source": [
    "This model takes user ids and movie titles, and outputs a predicted rating:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {
    "execution": {
     "iopub.execute_input": "2021-03-11T12:08:57.831422Z",
     "iopub.status.busy": "2021-03-11T12:08:57.830871Z",
     "iopub.status.idle": "2021-03-11T12:08:57.912343Z",
     "shell.execute_reply": "2021-03-11T12:08:57.912739Z"
    },
    "id": "YVxiAsRE2I8J"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "WARNING:tensorflow:Layers in a Sequential model should only have a single input tensor, but we receive a <class 'list'> input: ['42']\n",
      "Consider rewriting this model with the Functional API.\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "WARNING:tensorflow:Layers in a Sequential model should only have a single input tensor, but we receive a <class 'list'> input: ['42']\n",
      "Consider rewriting this model with the Functional API.\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "WARNING:tensorflow:Layers in a Sequential model should only have a single input tensor, but we receive a <class 'list'> input: [\"One Flew Over the Cuckoo's Nest (1975)\"]\n",
      "Consider rewriting this model with the Functional API.\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "WARNING:tensorflow:Layers in a Sequential model should only have a single input tensor, but we receive a <class 'list'> input: [\"One Flew Over the Cuckoo's Nest (1975)\"]\n",
      "Consider rewriting this model with the Functional API.\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "<tf.Tensor: shape=(1, 1), dtype=float32, numpy=array([[0.03740937]], dtype=float32)>"
      ]
     },
     "execution_count": 10,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "RankingModel()(([\"42\"], [\"One Flew Over the Cuckoo's Nest (1975)\"]))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "nCaCqJsXSkCo"
   },
   "source": [
    "### Loss and metrics\n",
    "\n",
    "The next component is the loss used to train our model. TFRS has several loss layers and tasks to make this easy.\n",
    "\n",
    "In this instance, we'll make use of the `Ranking` task object: a convenience wrapper that bundles together the loss function and metric computation. \n",
    "\n",
    "We'll use it together with the `MeanSquaredError` Keras loss in order to predict the ratings."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {
    "execution": {
     "iopub.execute_input": "2021-03-11T12:08:57.922112Z",
     "iopub.status.busy": "2021-03-11T12:08:57.921545Z",
     "iopub.status.idle": "2021-03-11T12:08:57.927924Z",
     "shell.execute_reply": "2021-03-11T12:08:57.928281Z"
    },
    "id": "tJ61Iz2QTBw3"
   },
   "outputs": [],
   "source": [
    "task = tfrs.tasks.Ranking(\n",
    "  loss = tf.keras.losses.MeanSquaredError(),\n",
    "  metrics=[tf.keras.metrics.RootMeanSquaredError()]\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "9-3xFC-1cbz0"
   },
   "source": [
    "The task itself is a Keras layer that takes true and predicted as arguments, and returns the computed loss. We'll use that to implement the model's training loop."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "FZUFeSlWRHGx"
   },
   "source": [
    "### The full model\n",
    "\n",
    "We can now put it all together into a model. TFRS exposes a base model class (`tfrs.models.Model`) which streamlines bulding models: all we need to do is to set up the components in the `__init__` method, and implement the `compute_loss` method, taking in the raw features and returning a loss value.\n",
    "\n",
    "The base model will then take care of creating the appropriate training loop to fit our model."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "metadata": {
    "execution": {
     "iopub.execute_input": "2021-03-11T12:08:57.934154Z",
     "iopub.status.busy": "2021-03-11T12:08:57.933593Z",
     "iopub.status.idle": "2021-03-11T12:08:57.935190Z",
     "shell.execute_reply": "2021-03-11T12:08:57.935567Z"
    },
    "id": "8n7c5CHFp0ow"
   },
   "outputs": [],
   "source": [
    "class MovielensModel(tfrs.models.Model):\n",
    "\n",
    "  def __init__(self):\n",
    "    super().__init__()\n",
    "    self.ranking_model: tf.keras.Model = RankingModel()\n",
    "    self.task: tf.keras.layers.Layer = tfrs.tasks.Ranking(\n",
    "      loss = tf.keras.losses.MeanSquaredError(),\n",
    "      metrics=[tf.keras.metrics.RootMeanSquaredError()]\n",
    "    )\n",
    "\n",
    "  def compute_loss(self, features: Dict[Text, tf.Tensor], training=False) -> tf.Tensor:\n",
    "    rating_predictions = self.ranking_model(\n",
    "        (features[\"user_id\"], features[\"movie_title\"]))\n",
    "\n",
    "    # The task computes the loss and the metrics.\n",
    "    return self.task(labels=features[\"user_rating\"], predictions=rating_predictions)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "yDN_LJGlnRGo"
   },
   "source": [
    "## Lab Task 3: Fitting and evaluating\n",
    "\n",
    "After defining the model, we can use standard Keras fitting and evaluation routines to fit and evaluate the model.\n",
    "\n",
    "Let's first instantiate the model."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "metadata": {
    "execution": {
     "iopub.execute_input": "2021-03-11T12:08:57.941237Z",
     "iopub.status.busy": "2021-03-11T12:08:57.940637Z",
     "iopub.status.idle": "2021-03-11T12:08:57.981595Z",
     "shell.execute_reply": "2021-03-11T12:08:57.981958Z"
    },
    "id": "aW63YaqP2wCf"
   },
   "outputs": [],
   "source": [
    "model = MovielensModel()\n",
    "model.compile(optimizer=tf.keras.optimizers.Adagrad(learning_rate=0.1))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "Nma0vc2XdN5g"
   },
   "source": [
    "Then shuffle, batch, and cache the training and evaluation data."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "metadata": {
    "execution": {
     "iopub.execute_input": "2021-03-11T12:08:57.985739Z",
     "iopub.status.busy": "2021-03-11T12:08:57.985109Z",
     "iopub.status.idle": "2021-03-11T12:08:57.988718Z",
     "shell.execute_reply": "2021-03-11T12:08:57.989081Z"
    },
    "id": "53QJwY1gUnfv"
   },
   "outputs": [],
   "source": [
    "cached_train = train.shuffle(100_000).batch(8192).cache()\n",
    "cached_test = test.batch(4096).cache()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "u8mHTxKAdTJO"
   },
   "source": [
    "Then train the  model:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "metadata": {
    "execution": {
     "iopub.execute_input": "2021-03-11T12:08:57.992879Z",
     "iopub.status.busy": "2021-03-11T12:08:57.992336Z",
     "iopub.status.idle": "2021-03-11T12:08:59.443492Z",
     "shell.execute_reply": "2021-03-11T12:08:59.443059Z"
    },
    "id": "ZxPntlT8EFOZ"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Epoch 1/3\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\r",
      " 1/10 [==>...........................] - ETA: 8s - root_mean_squared_error: 3.7305 - loss: 13.9166 - regularization_loss: 0.0000e+00 - total_loss: 13.9166"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\r",
      " 3/10 [========>.....................] - ETA: 0s - root_mean_squared_error: 2.9011 - loss: 8.4161 - regularization_loss: 0.0000e+00 - total_loss: 8.4161  "
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\r",
      " 6/10 [=================>............] - ETA: 0s - root_mean_squared_error: 2.6217 - loss: 6.8733 - regularization_loss: 0.0000e+00 - total_loss: 6.8733"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\r",
      " 9/10 [==========================>...] - ETA: 0s - root_mean_squared_error: 2.2382 - loss: 5.0095 - regularization_loss: 0.0000e+00 - total_loss: 5.0095"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\r",
      "10/10 [==============================] - 1s 24ms/step - root_mean_squared_error: 2.1718 - loss: 4.3303 - regularization_loss: 0.0000e+00 - total_loss: 4.3303\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Epoch 2/3\n",
      "\r",
      " 1/10 [==>...........................] - ETA: 0s - root_mean_squared_error: 1.1170 - loss: 1.2476 - regularization_loss: 0.0000e+00 - total_loss: 1.2476"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\r",
      " 7/10 [====================>.........] - ETA: 0s - root_mean_squared_error: 1.1246 - loss: 1.2646 - regularization_loss: 0.0000e+00 - total_loss: 1.2646"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\r",
      "10/10 [==============================] - 0s 8ms/step - root_mean_squared_error: 1.1227 - loss: 1.2602 - regularization_loss: 0.0000e+00 - total_loss: 1.2602\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Epoch 3/3\n",
      "\r",
      " 1/10 [==>...........................] - ETA: 0s - root_mean_squared_error: 1.1097 - loss: 1.2315 - regularization_loss: 0.0000e+00 - total_loss: 1.2315"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\r",
      " 8/10 [=======================>......] - ETA: 0s - root_mean_squared_error: 1.1167 - loss: 1.2471 - regularization_loss: 0.0000e+00 - total_loss: 1.2471"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\r",
      "10/10 [==============================] - 0s 8ms/step - root_mean_squared_error: 1.1162 - loss: 1.2456 - regularization_loss: 0.0000e+00 - total_loss: 1.2456\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "<keras.callbacks.History at 0x7f96f389bc50>"
      ]
     },
     "execution_count": 15,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "model.fit(cached_train, epochs=3)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "YsluR8audV9W"
   },
   "source": [
    "As the model trains, the loss is falling and the RMSE metric is improving."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "7Gxp5RLFcv64"
   },
   "source": [
    "Finally, we can evaluate our model on the test set:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "metadata": {
    "execution": {
     "iopub.execute_input": "2021-03-11T12:08:59.448036Z",
     "iopub.status.busy": "2021-03-11T12:08:59.447483Z",
     "iopub.status.idle": "2021-03-11T12:08:59.989853Z",
     "shell.execute_reply": "2021-03-11T12:08:59.990279Z"
    },
    "id": "W-zu6HLODNeI"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\r",
      "1/5 [=====>........................] - ETA: 1s - root_mean_squared_error: 1.1169 - loss: 1.2475 - regularization_loss: 0.0000e+00 - total_loss: 1.2475"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\r",
      "5/5 [==============================] - 1s 10ms/step - root_mean_squared_error: 1.1108 - loss: 1.2287 - regularization_loss: 0.0000e+00 - total_loss: 1.2287\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "{'root_mean_squared_error': 1.1108061075210571,\n",
       " 'loss': 1.2062578201293945,\n",
       " 'regularization_loss': 0,\n",
       " 'total_loss': 1.2062578201293945}"
      ]
     },
     "execution_count": 16,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# TODO 3a -- your code goes here"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "JKZyP9A1dxit"
   },
   "source": [
    "The lower the RMSE metric, the more accurate our model is at predicting ratings."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "efApI0Ii6srB"
   },
   "source": [
    "## Next steps\n",
    "\n",
    "The model above gives us a decent start towards building a ranking system.\n",
    "\n",
    "Of course, making a practical ranking system requires much more effort.\n",
    "\n",
    "In most cases, a ranking model can be substantially improved by using more features rather than just user and candidate identifiers. To see how to do that, have a look at the [side features](https://www.tensorflow.org/recommenders/examples/featurization) tutorial.\n",
    "\n",
    "A careful understanding of the objectives worth optimizing is also necessary. To get started on building a recommender that optimizes multiple objectives, have a look at our [multitask](https://www.tensorflow.org/recommenders/examples/multitask) tutorial."
   ]
  }
 ],
 "metadata": {
  "colab": {
   "collapsed_sections": [],
   "name": "basic_ranking.ipynb",
   "private_outputs": true,
   "provenance": [],
   "toc_visible": true
  },
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.6.9"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 0
}
